package com.gomcarter.frameworks.base.generator.openx;

import com.google.common.io.Files;
import com.yifeng.repo.base.utils.file.FileHelper;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Openx控制层代码生成器
 *
 * @author zyh
 * create on 2023/4/3 10:34
 */
@Slf4j
public class OpenxGenerator {
    private static final List<String> IGNORE_METHOD_NAME_LIST = Arrays.asList("wait", "equals", "toString", "hashCode",
            "getClass", "notify", "notifyAll");
    private static final List<String> IGNORE_TYPE_NAME_LIST = Arrays.asList("byte", "boolean", "char", "shot", "int",
            "long", "float", "double", "void");
    private static final String COLLECTION_TYPE_NAME = "java.util.List, java.util.Map, java.util.Set";

    /**
     * 通过openx service层生成控制层代码
     *
     * @param controlPackage 控制层包路径
     * @param servicePackage 服务层包路径
     */
    public static void generator(String controlPackage, String controlPackageName, String servicePackage, boolean isBreak) throws IOException, TemplateException {
        // file check
        File controlDir = new File(controlPackage);
        if (controlDir.exists()) {
            if (controlDir.isFile()) {
                throw new RuntimeException("controlPackage can not be a file");
            }
        } else {
            controlDir.mkdirs();
        }
        File serviceDir = new File(servicePackage);
        if (serviceDir.exists()) {
            if (serviceDir.isFile()) {
                throw new RuntimeException("servicePackage can not be a file");
            }
        } else {
            throw new RuntimeException("serviceDir not exist!");
        }
        // 渲染文件
        List<File> serviceFiles = FileHelper.listSubFile(serviceDir, true);
        for (File serviceFile : serviceFiles) {
            if (serviceFile.getName().endsWith("Impl.java")) {
                String servicePackageName = Files.readLines(serviceFile, StandardCharsets.UTF_8).get(0);
                servicePackageName = servicePackageName.replace("package", "");
                servicePackageName = servicePackageName.replace(";", "");
                String serviceClassName = servicePackageName + "." + serviceFile.getName().replace(".java", "");
                Class<?> serviceClass;
                try {
                    serviceClass = Class.forName(serviceClassName.trim());
                } catch (ClassNotFoundException e) {
                    log.error("not found class for serviceClassName={}", serviceClassName.trim());
                    continue;
                }
                Class<?>[] interfaces = serviceClass.getInterfaces();
                if (interfaces.length == 0) {
                    log.warn("not found interface for serviceClassName={}", serviceClassName.trim());
                    continue;
                }
                List<String> importList = new ArrayList<>();
                for (Class<?> anInterface : interfaces) {
                    importList.add(anInterface.toString().replace("interface", "").trim());
                }
                Map<String, Object> data = new HashMap<>();
                String serviceSimpleName = serviceClass.getSimpleName().replace("Impl", "");
                String controllerName = serviceSimpleName + "Controller";
                data.put("packageName", controlPackageName);
                data.put("requestMapping", serviceClass.getAnnotation(Service.class).value());
                data.put("controllerName", controllerName);
                data.put("serviceName", serviceSimpleName);
                data.put("name", lowerFirst(serviceSimpleName));
                List<Method> methodList = new ArrayList<>();
                java.lang.reflect.Method[] methods = serviceClass.getMethods();
                for (java.lang.reflect.Method method : methods) {
                    // TODO:排除公共方法和其他非接口方法 ->要支持数组形式 int[]
                    if (IGNORE_METHOD_NAME_LIST.contains(method.getName())) {
                        continue;
                    }
                    Method targetMethod = new Method();
                    targetMethod.setMethodName(method.getName());
                    String returnTypeName = method.getReturnType().getTypeName();
                    addImport(importList, returnTypeName);
                    // 属于List<?>,Map<?>等
                    if (isGeneric(returnTypeName)) {
                        Type genericReturnType = method.getGenericReturnType();
                        // 如果不包含<>就跳过实际参数拼接
                        String wholeTypeName = genericReturnType.getTypeName();
                        if(wholeTypeName.contains("<")) {
                            Type[] actualTypeArguments = ((ParameterizedTypeImpl) genericReturnType).getActualTypeArguments();
                            if (actualTypeArguments != null && actualTypeArguments.length > 0) {
                                StringBuilder returnTypeStringBuilder = new StringBuilder();
                                for (Type actualTypeArgument : actualTypeArguments) {
                                    String actualTypeName = actualTypeArgument.getTypeName();
                                    //TODO:里面的数据还是List等类型
                                    if (isGeneric(actualTypeName)) {

                                    } else {
                                        addImport(importList, actualTypeName);
                                        returnTypeStringBuilder.append(getLast(actualTypeName)).append(",");
                                    }
                                }
                                if(returnTypeStringBuilder.length() > 0) {
                                    returnTypeStringBuilder.deleteCharAt(returnTypeStringBuilder.length() - 1);
                                    returnTypeName = getLast(returnTypeName) + "<" + returnTypeStringBuilder + ">";
                                } else {
                                    returnTypeName = getLast(returnTypeName);
                                }
                            }
                        }
                    } else {
                        returnTypeName = getLast(returnTypeName);
                    }
                    targetMethod.setReturnType(returnTypeName);
                    List<Param> paramList = new ArrayList<>();
                    Parameter[] parameters = method.getParameters();
                    Type[] genericParameterTypes = method.getGenericParameterTypes();
                    for (int i = 0; i < parameters.length; i++) {
                        Parameter parameter = parameters[i];
                        String paramType = parameter.getType().getTypeName();
                        Param targetParam = new Param();
                        if (isGeneric(paramType)) {
                            addImport(importList, paramType);
                            Type genericParameterType = genericParameterTypes[i];
                            String wholeParamTypeName = genericParameterType.getTypeName();
                            if(wholeParamTypeName.contains("<")) {
                                ParameterizedTypeImpl pt = (ParameterizedTypeImpl) genericParameterType;
                                paramType = paramType.replaceAll("<.*.>", "");
                                importList.add(paramType.replace("interface", "").trim());
                                Type[] actualTypeArguments = pt.getActualTypeArguments();
                                StringBuilder paramTypeStringBuilder = new StringBuilder();
                                for (Type actualTypeArgument : actualTypeArguments) {
                                    String actualTypeArgumentName = actualTypeArgument.getTypeName();
                                    if (!isBasicData(actualTypeArgumentName)) {
                                        addImport(importList, actualTypeArgumentName);
                                    }
                                    if (isGeneric(actualTypeArgumentName)) {
                                        // FIXME:暂时支持添加一级类
                                        paramTypeStringBuilder.append(getLast(actualTypeArgumentName)).append(",");
                                    } else {
                                        paramTypeStringBuilder.append(getLast(actualTypeArgumentName)).append(",");
                                    }
                                }
                                // FIXME:此处只是因为isGeneric里还没有做处理，可能导致paramTypeStringBuilder未空
                                if(paramTypeStringBuilder.length() > 0) {
                                    paramTypeStringBuilder.deleteCharAt(paramTypeStringBuilder.length() - 1);
                                    paramType = getLast(paramType) + "<" + paramTypeStringBuilder + ">";
                                } else {
                                    paramType = getLast(paramType);
                                }
                            } else {
                                // 只写了List、Map这种形式
                                paramType = getLast(paramType);
                            }
                        } else {
                            addImport(importList, paramType);
                            paramType = getLast(paramType);
                        }
                        targetParam.setType(paramType);
                        if (i == genericParameterTypes.length - 1) {
                            targetParam.setName(parameter.getName());
                        } else {
                            targetParam.setName(parameter.getName() + ", ");
                        }
                        paramList.add(targetParam);
                    }
                    targetMethod.setParamList(paramList);
                    Class<?>[] exceptionTypes = method.getExceptionTypes();
                    List<String> exceptionList = new ArrayList<>();
                    for (int i = 0; i < exceptionTypes.length; i++) {
                        Class<?> exceptionType = exceptionTypes[i];
                        String typeName = exceptionType.getTypeName();
                        importList.add(typeName);
                        typeName = typeName.substring(typeName.lastIndexOf(".") + 1);
                        if (i == exceptionTypes.length - 1) {
                            exceptionList.add(typeName);
                        } else {
                            exceptionList.add(typeName + ", ");
                        }
                    }
                    targetMethod.setExceptionList(exceptionList);
                    methodList.add(targetMethod);
                }
                data.put("methodList", methodList);
                data.put("importList", importList.stream().distinct().collect(Collectors.toList()));
                Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
                configuration.setDefaultEncoding(StandardCharsets.UTF_8.name());
                configuration.setClassForTemplateLoading(OpenxGenerator.class, "/template/");
                Template template = configuration.getTemplate("openx-controller.ftl");
                File controllerFile = new File(controlPackage + File.separator + controllerName + ".java");
                template.process(data, new FileWriter(controllerFile));
                importList.clear();
                log.info("make {} success", serviceClassName);
                if(isBreak) {
                    break;
                }
            }
        }
    }

    private static String getLast(String str) {
        return str.substring(str.lastIndexOf(".") + 1);
    }

    private static String lowerFirst(String str) {
        char[] cs = str.toCharArray();
        cs[0] += 32;
        return String.valueOf(cs);
    }

    private static boolean isBasicData(String type) {
        return IGNORE_TYPE_NAME_LIST.contains(type);
    }

    private static boolean isGeneric(String typeName) {
        // FIXME:此处的判断不严谨
        return COLLECTION_TYPE_NAME.contains(typeName);
    }

    private static void addImport(List<String> importList, String type) {
        if (!isBasicData(type)) {
            importList.add(type.replace("interface", "").trim());
        }
    }
}
